fix(cli/ios): produce App Store-ready IPA from dx bundle (closes #3817)#5591
Open
mirkobozzetto wants to merge 3 commits into
Open
fix(cli/ios): produce App Store-ready IPA from dx bundle (closes #3817)#5591mirkobozzetto wants to merge 3 commits into
dx bundle (closes #3817)#5591mirkobozzetto wants to merge 3 commits into
Conversation
Adds the keys Apple Transporter / altool validation has required since iOS 14 / 2024 App Store rules: - ITSAppUsesNonExemptEncryption=false default (skips per-upload export compliance prompt; apps using non-exempt crypto can override via Dioxus.toml `[ios.plist]`). - Modern `UILaunchScreen` dict replaces `UILaunchStoryboardName`. Apple rejects iPad-supporting bundles that reference a missing storyboard; the dict requires no storyboard file (iOS 14+). - `CFBundleSupportedPlatforms` reduced to a single `iPhoneOS` value. Apple rejects `[iPhoneOS, iPadOS]` — iPadOS is not a valid platform identifier here, the binary is iPhoneOS regardless of device family. - Placeholders for `DT*` / `MinimumOSVersion` / `CFBundlePackageType` metadata wired into the template via handlebars guards; the Rust side populates them in the next commit. Part of the fix for DioxusLabs#3817.
Adds a small `IosDtMetadata` struct and `collect_ios_dt_metadata` helper
that probes the local SDK / Xcode install (`xcrun --sdk iphoneos
--show-sdk-version`, `defaults read .../DTXcode`, `sw_vers
-buildVersion`) and feeds the values into the handlebars iOS Info.plist
template.
Apple Transporter / altool reject App Store IPAs that omit
`DTPlatformName`, `DTPlatformVersion`, `DTSDKName`, `DTSDKBuild`,
`DTXcode`, `DTXcodeBuild`, `DTCompiler`, `DTPlatformBuild`,
`BuildMachineOSBuild`, `MinimumOSVersion`, or `CFBundlePackageType` —
the same set Xcode injects when it builds an iOS app.
Each probe is best-effort: a failure leaves the field empty and the
template's `{{#if}}` guards drop the key, so non-developer machines
still produce a valid `.app` (just not one App Store will accept).
`MinimumOSVersion` is sourced from `Dioxus.toml`'s
`[ios].deployment_target` (defaults to `15.0`).
Part of the fix for DioxusLabs#3817.
…DioxusLabs#3817) Adds an opt-in `--appstore` flag to `dx bundle --platform=ios` that completes the chain Apple App Store / Transporter / altool require for upload. Default behavior (dev signing) is unchanged. When `--appstore` is set: - `auto_provision_signing_name` looks for an "Apple Distribution" identity instead of "Apple Development". - `auto_provision_entitlements` filters provisioning profile candidates to those without a `ProvisionedDevices` key (= App Store distribution profiles, not dev / ad-hoc). - Generated entitlements use `get-task-allow=false` (Apple ITMS rejects App Store uploads where the key is `true`). - `should_codesign` is forced on so `dx bundle` produces a signed `.app` even without an explicit `--codesign` flag. Regardless of `--appstore`, iOS bundles are now App Store-shaped: - Nested `.appex` extensions under `PlugIns/` are signed inside-out with their own provisioning profiles (matched by each extension's CFBundleIdentifier). Apple rejects bundles whose nested extensions ship unsigned or with stale dev certs. - `AppIcon.xcassets` at the crate root is compiled via `xcrun actool` and the resulting CFBundleIcons keys are merged into the main Info.plist. Apple rejects iOS bundles missing the 120x120 icon. - `ios/PrivacyInfo.xcprivacy`, if present at the crate root, is copied into the bundle. Apple has required PrivacyInfo on App Store submissions since 2024. - Widget / extension Info.plists are patched with the same DT* metadata, version strings (CFBundleVersion + CFBundleShortVersionString parity), `LSRequiresIPhoneOS`, `CFBundleSupportedPlatforms`, and `UIRequiredDeviceCapabilities` the main bundle carries. End-to-end verified against a real iOS app (FlowFlow) using `xcrun altool --validate-app -f <ipa> -t ios`: Apple responds `VERIFY SUCCEEDED with no errors`. All bundle-content modifications happen before any `codesign` call so the signature covers the final bundle contents. Closes DioxusLabs#3817.
96952df to
119125b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds end-to-end App Store support to
dx bundle --platform=ios --release. Closes #3817 (open since 2025-03-01, 6 reactions). A new opt-in--appstoreflag switches signing to Apple Distribution; existing dev signing behavior is unchanged when the flag is absent.Verified against a real app (FlowFlow, already shipping on TestFlight):
xcrun altool --validate-appreturnsVERIFY SUCCEEDED with no errors.Problem
dx bundle --platform=ios --releaseproduces an.ipaApple Transporter / altool rejects. The original report (#3817) showsValidation failed (409) Incorrect Platform, but full validation surfaces a chain of related gaps:Info.plistis missing theDT*/MinimumOSVersion/CFBundlePackageTypekeys Apple injects from the Xcode toolchainCFBundleSupportedPlatformsships as[iPhoneOS, iPadOS](Apple requires a single value)UILaunchStoryboardName=LaunchScreenreferences a storyboard the build never produces (Apple rejects iPad-supporting bundles for this)get-task-allow=true(Apple ITMS rejects on App Store upload).appexextensions underPlugIns/ship unsigned (or with stale dev certs)AppIcon.xcassetsis never compiled — Apple rejects bundles missing the 120x120 iPhone iconPrivacyInfo.xcprivacyis never copied into the bundle (required since 2024)The bash workaround most users land on (e.g. FlowFlow's
Makefile) reproduces this exact chain — this PR upstreams it.Changes
Three commits, ordered so each compiles standalone:
fix(cli/ios): modernize Info.plist defaults for App Store compliance— template-only changes.ITSAppUsesNonExemptEncryption=false, modernUILaunchScreendict (iOS 14+), singleiPhoneOSinCFBundleSupportedPlatforms, handlebars{{#if}}slots for the DT* values the next commit populates.feat(cli/ios): probe Xcode toolchain to populate Info.plist DT* metadata— addsIosDtMetadata+collect_ios_dt_metadata(xcrun / defaults / sw_vers probes). Best-effort: empty fields are dropped by the template guards, so machines without a full Xcode install still produce a.app.MinimumOSVersionreads fromDioxus.toml's[ios].deployment_target(default15.0).feat(cli/ios): produce App Store-ready IPA via --appstore flag (closes #3817)— adds the--appstoreclap flag (packages/cli/src/cli/target.rs) and wires it throughBuildRequest. When set: cert pattern switches toApple Distribution, profile auto-discovery filters out profiles withProvisionedDevices, generated entitlements useget-task-allow=false, andshould_codesignis forced on. Independently of the flag (every iOS bundle benefits), iOS pre-sign now:PlugIns/*.appexinside-out with its own provisioning profile,AppIcon.xcassetsat the crate root viaxcrun actooland merges the partial Info.plist back into the main one,ios/PrivacyInfo.xcprivacyinto the.appif present,.appexInfo.plist with the DT*, version strings (CFBundleVersion+CFBundleShortVersionStringparity),LSRequiresIPhoneOS,CFBundleSupportedPlatforms=[iPhoneOS], andUIRequiredDeviceCapabilities=[arm64]the main bundle carries.Default behavior (no flag, no widget, no xcassets, no PrivacyInfo) is identical to today's CLI — every new code path is gated behind feature detection or the explicit flag.
Proof
End-to-end validation against FlowFlow, using
xcrun altool --validate-appagainst Apple's server-side validator:The bundle Apple validated contains a Live Activity widget extension (
recording_widget.appex), background audio mode, andPrivacyInfo.xcprivacy— i.e. all the non-trivial App Store features the PR touches.Out of scope
These keep the PR focused; happy to follow up:
CFBundleVersionauto-bump strategy (compile-time / Cargo.toml / external counter)Test plan
cargo build -p dioxus-clicleancargo fmt -p dioxus-cli --checkcleancargo clippy -p dioxus-cli --no-depsno findingsdx bundle --platform=ios --releasewithout--appstore: behavior unchanged (dev signing, no Apple Distribution lookup, get-task-allow stays true)dx bundle --platform=ios --release --appstore: produces IPA Apple altool accepts (see Proof)